// ----------------------------------------------------------------------------
// Copyright 2006, Martin D. Flynn
// All rights reserved
// ----------------------------------------------------------------------------
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// 
// http://www.apache.org/licenses/LICENSE-2.0
// 
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// ----------------------------------------------------------------------------
// Description:
//  This class provides the options screen used for editing specific properties
//  used by the OpenDMTP protocol.
// ----------------------------------------------------------------------------
// Change History:
//  2006/03/26  Martin D. Flynn
//      Initial release
//  2006/03/31  Martin D. Flynn
//      Placed in custom sub-package to allow device dependent UI packaging
// ----------------------------------------------------------------------------
package org.opendmtp.j2me.client.custom.treo650;

import javax.microedition.lcdui.Command;
import javax.microedition.lcdui.CommandListener;
import javax.microedition.lcdui.Displayable;
import javax.microedition.lcdui.Form;
import javax.microedition.lcdui.StringItem;
import javax.microedition.lcdui.TextField;

import org.opendmtp.j2me.client.base.Props;
import org.opendmtp.j2me.util.Log;
import org.opendmtp.j2me.util.StringTools;

/**
 * Provides the options screen displayed on the device used for editing specific
 * properties used by the OpenDMTP protocol.
 * 
 * @author Martin D. Flynn
 * @author Robert S. Brewer
 */

public class OptionsScreen extends Form implements CommandListener {

  // ------------------------------------------------------------------------

  /**
   * Name used when logging error messages.
   */
  private static final String LOG_NAME = "HOST";

  // ------------------------------------------------------------------------
  // See 'DMTPProps.java' and 'Props.java' for a list of other configurable
  // properties that could be added to this options editor.

  /**
   * Contains value of PROP_COMM_DMTP_HOST in interface.
   */
  private TextField hostFld = null; // Props.PROP_COMM_DMTP_HOST

  /**
   * Contains value of PROP_COMM_DMTP_PORT in interface.
   */
  private TextField portFld = null; // Props.PROP_COMM_DMTP_PORT
  
  /**
   * Contains value of PROP_STATE_ACCOUNT_ID in interface.
   */
  private TextField acctFld = null; // Props.PROP_STATE_ACCOUNT_ID
  
  /**
   * Contains value of PROP_STATE_DEVICE_ID in interface.
   */
  private TextField devFld = null; // Props.PROP_STATE_DEVICE_ID
  
  /**
   * Contains value of PROP_MOTION_IN_MOTION in interface.
   */
  private TextField inmoFld = null; // Props.PROP_MOTION_IN_MOTION

  /**
   * Contains value of PROP_MOTION_DORMANT_INTRVL in interface.
   */
  private TextField dormFld = null; // Props.PROP_MOTION_DORMANT_INTRVL

  
  /**
   * Used to display error messages when validating user data.
   */
  private StringItem errMsg = null;

  
  /**
   * Command executed when OK button selected. 
   */
  private Command cmdOK = null;
  /**
   * Command executed when Cancel button selected. 
   */
  private Command cmdCancel = null;

  
  /**
   * Builds up the OptionsScreen Form from fields and buttons.
   */
  private OptionsScreen() {
    super("OpenDMTP Options");

    /* fields */
    try {
      // host
      super.append(new StringItem("Remote server host name:\n", null));
      this.hostFld = new TextField(null, "", 24, TextField.ANY);
      super.append(this.hostFld);
      // port
      super.append(new StringItem("Remote server host port:\n", null));
      this.portFld = new TextField(null, "", 5, TextField.DECIMAL);
      super.append(this.portFld);
      // account-id
      super.append(new StringItem("Account ID:\n", null));
      this.acctFld = new TextField(null, "", Props.MAX_ID_SIZE, TextField.ANY);
      super.append(this.acctFld);
      // device-id
      super.append(new StringItem("Device ID:\n", null));
      this.devFld = new TextField(null, "", Props.MAX_ID_SIZE, TextField.ANY);
      super.append(this.devFld);
      // in-motion interval
      super.append(new StringItem("In-Motion interval (seconds):\n", null));
      this.inmoFld = new TextField(null, "", 5, TextField.DECIMAL);
      super.append(this.inmoFld);
      // dormant interval
      super.append(new StringItem("Dormant interval (seconds):\n", null));
      this.dormFld = new TextField(null, "", 5, TextField.DECIMAL);
      super.append(this.dormFld);
      // error message
      this.errMsg = new StringItem("\n", null);
      super.append(this.errMsg);
    }
    catch (Throwable t) {
      Log.error(LOG_NAME, "Field init", t);
    }

    /* commands */
    try {
      this.cmdOK = new Command("OK", Command.OK, 1);
      this.addCommand(this.cmdOK);
      this.cmdCancel = new Command("Cancel", Command.CANCEL, 3);
      this.addCommand(this.cmdCancel);
      super.setCommandListener(this);
    }
    catch (Throwable t) {
      Log.error(LOG_NAME, "Command init", t);
    }

  }

  // ----------------------------------------------------------------------------

  /**
   * Holds the singleton OptionScreen instance.
   */
  private static OptionsScreen hostScreen = null;

  /**
   * Builds new hostScreen if this is the first time showing, and then 
   * passes actual display to _show(). 
   */
  public static void show() {
    if (OptionsScreen.hostScreen == null) {
      OptionsScreen.hostScreen = new OptionsScreen();
    }
    OptionsScreen.hostScreen._show();
  }

  /**
   * Gathers the values of the various text fields from Properties, validates the
   * values, and then actually displays them.
   */
  private void _show() {
    // host
    String hostStr = Props.getString(Props.PROP_COMM_DMTP_HOST, "");
    this.hostFld.setString(hostStr);
    // port
    String portStr = Props.getString(Props.PROP_COMM_DMTP_PORT, "0");
    this.portFld.setString(portStr);
    // account-id
    String acctStr = Props.getString(Props.PROP_STATE_ACCOUNT_ID, "example");
    this.acctFld.setString(acctStr);
    // device-id
    String devStr = Props.getString(Props.PROP_STATE_DEVICE_ID, "mobile");
    this.devFld.setString(devStr);
    // in-motion interval
    String inmoStr = Props.getString(Props.PROP_MOTION_IN_MOTION, "0");
    this.inmoFld.setString(inmoStr);
    // dormant interval
    String dormStr = Props.getString(Props.PROP_MOTION_DORMANT_INTRVL, "0");
    this.dormFld.setString(dormStr);
    // error message
    this.validateEntries();
    // display
    Main.showDisplayable(this);
  }

  // ----------------------------------------------------------------------------
  // CommandListener interface

  /**
   * Validates all the field entries, setting errMsg appropriately if any values are
   * invalid. If there are multiple validation errors, errMsg will only contain the
   * message about the first error found.
   * @return true if all field entries were valid.
   */
  private boolean validateEntries() {
    boolean rtn = false;
    String msg = null;
    if (!OptionsScreen.isValidHost(this.hostFld.getString().trim())) {
      msg = "Enter valid host name";
    }
    else if (!OptionsScreen.isValidPort(this.portFld.getString().trim())) {
      msg = "Enter valid host port";
    }
    else if (!OptionsScreen.isValidAccountID(this.acctFld.getString().trim())) {
      msg = "Enter valid account id";
    }
    else if (!OptionsScreen.isValidDeviceID(this.devFld.getString().trim())) {
      msg = "Enter valid device id";
    }
    else if (!OptionsScreen.isValidInterval(this.inmoFld.getString().trim(), 60L)) {
      msg = "Enter valid in-motion interval [60-3600]";
    }
    else if (!OptionsScreen.isValidInterval(this.dormFld.getString().trim(), 300L)) {
      msg = "Enter valid dormant interval [300-3600]";
    }
    else {
      msg = "";
      rtn = true;
    }
    this.errMsg.setLabel("\n" + msg + "\n");
    return rtn;
  }

  /**
   * Handles commands from the form interface, OK and cancel. Handles data verification,
   * and storage of field values.
   * 
   * @param c Command to be executed
   * @param d form being displayed, currently unused.
   * @see javax.microedition.lcdui.CommandListener#commandAction(javax.microedition.lcdui.Command, javax.microedition.lcdui.Displayable)
   */
  public void commandAction(Command c, Displayable d) {
    try {
      if (c == this.cmdOK) {
        if (this.validateEntries()) {
          // host
          String hostStr = this.hostFld.getString().trim();
          Props.initFromString(Props.PROP_COMM_DMTP_HOST, hostStr);
          Props.setChanged(Props.PROP_COMM_DMTP_HOST);
          // port
          String portStr = this.portFld.getString().trim();
          Props.initFromString(Props.PROP_COMM_DMTP_PORT, portStr);
          Props.setChanged(Props.PROP_COMM_DMTP_PORT);
          // account-id
          String acctStr = this.acctFld.getString().trim();
          Props.initFromString(Props.PROP_STATE_ACCOUNT_ID, acctStr);
          Props.setChanged(Props.PROP_STATE_ACCOUNT_ID);
          // device-id
          String devStr = this.devFld.getString().trim();
          Props.initFromString(Props.PROP_STATE_DEVICE_ID, devStr);
          Props.setChanged(Props.PROP_STATE_DEVICE_ID);
          // in-motion
          String inmoStr = this.inmoFld.getString().trim();
          Props.initFromString(Props.PROP_MOTION_IN_MOTION, inmoStr);
          Props.setChanged(Props.PROP_MOTION_IN_MOTION);
          // dormant
          String dormStr = this.dormFld.getString().trim();
          Props.initFromString(Props.PROP_MOTION_DORMANT_INTRVL, dormStr);
          Props.setChanged(Props.PROP_MOTION_DORMANT_INTRVL);
          Main.getInstance().saveProps();
          Main.showGPSDisplayable();
        }
      }
      else if (c == this.cmdCancel) {
        Main.showGPSDisplayable();
      }
    }
    catch (Throwable t) {
      AlertScreen.showError("Error", "Command: " + t);
    }
  }

  // ----------------------------------------------------------------------------

  
  /**
   * Valid characters for hostnames.
   */
  private static String ValidHostChar = "._-0123456789abcdefghijklmnopqrstuvwxyz";
  /**
   * Valid characters for device names.
   */
  private static String ValidDeviceChar = "._-0123456789abcdefghijklmnopqrstuvwxyz";
  /**
   * Valid characters for account names.
   */
  private static String ValidAccountChar = "._-0123456789abcdefghijklmnopqrstuvwxyz";

  /**
   * Checks whether a String is a valid hostname: non-null, non-empty, and consisting
   * only of valid characters.
   *  
   * @param hostStr String to be checked.
   * @return true if hostStr is valid, or false if it is not.
   */
  public static boolean isValidHost(String hostStr) {

    /* not specified */
    if ((hostStr == null) || hostStr.equals("")) {
      return false;
    }

    /* check host name for valid chars */
    String h = hostStr.toLowerCase();
    for (int i = 0; i < h.length(); i++) {
      char ch = h.charAt(i);
      if (ValidHostChar.indexOf(ch) < 0) {
        return false;
      }
    }
    return true;

  }

  /**
   * Checks whether a String contains a valid port number: greater than 0 and
   * less than 65535.
   * 
   * @param portStr String to be checked
   * @return true if portStr is valid, or false if it is not.
   */
  public static boolean isValidPort(String portStr) {

    /* check valid port range */
    int port = (int) StringTools.parseLong(portStr, 0L);
    if ((port > 0) || (port <= 0xFFFF)) {
      return true;
    }
    else {
      return false;
    }

  }

  /**
   * Checks whether a String is a valid account ID: non-null, non-empty, and consisting
   * only of valid characters.
   *  
   * @param acctStr String to be checked.
   * @return true if acctStr is valid, or false if it is not.
   */
  public static boolean isValidAccountID(String acctStr) {

    /* not specified */
    if ((acctStr == null) || acctStr.equals("")) {
      return false;
    }

    /* check account id for valid chars */
    String h = acctStr.toLowerCase();
    for (int i = 0; i < h.length(); i++) {
      char ch = h.charAt(i);
      if (ValidAccountChar.indexOf(ch) < 0) {
        return false;
      }
    }
    return true;

  }

  /**
   * Checks whether a String is a valid device ID: non-null, non-empty, and consisting
   * only of valid characters.
   *  
   * @param devStr String to be checked.
   * @return true if devStr is valid, or false if it is not.
   */
  public static boolean isValidDeviceID(String devStr) {

    /* not specified */
    if ((devStr == null) || devStr.equals("")) {
      return false;
    }

    /* check device id for valid chars */
    String h = devStr.toLowerCase();
    for (int i = 0; i < h.length(); i++) {
      char ch = h.charAt(i);
      if (ValidDeviceChar.indexOf(ch) < 0) {
        return false;
      }
    }
    return true;

  }

  /**
   * Checks whether a String is a valid event-sending interval: greater than the provided
   * minimum value or 0. Note that despite what calls to this method seem to assume, it
   * does NOT check if the interval value is greater than some maximum value. This would
   * appear to be a bug.
   *
   * @param interStr String to be checked.
   * @param minimum minimum boundary to be checked against.
   * @return true if interStr is valid, or false if it is not.
   */
  public static boolean isValidInterval(String interStr, long minimum) {

    /* check valid interval range */
    long interval = StringTools.parseLong(interStr, -1L);
    if (interval == 0L) {
      return true;
    }
    else if (interval >= minimum) {
      return true;
    }
    else {
      return false;
    }

  }

  // ----------------------------------------------------------------------------

}
